<?php
/**
 * DooModel class file.
 *
 * @author Leng Sheng Hong <darkredz@gmail.com>
 * @link http://www.doophp.com/
 * @copyright Copyright &copy; 2009 Leng Sheng Hong
 * @license http://www.doophp.com/license
 */


/**
 * DooModel is the base class for Model classes in DooPHP that provides useful querying methods.
 *
 * <p>The model classes can extend DooModel for more powerful ORM features which enable you to write shorter codes.
 * Extending this class is optional.</p>
 *
 * <p>All the extra ORM methods can be accessed in a static and non-static way. Example:</p>
 *
 * <code>
 * $food = new Food;
 * $food->getOne();
 * Food::_getOne();
 *
 * $food->count();
 * Food::_count();
 *
 * //Dynamic querying
 * $food->getById(14);
 * $food->getById_location(14, 'Malaysia');
 *
 * //Get only one item
 * $food->getById_first(14);
 * $food->getById_location_first(14, 'Malaysia');
 *
 * //Gets list of food with its food type
 * $food->relateFoodType();
 * $food->relateFoodType($food, array('limit'=>'first'));
 * $food->relateFoodType_first();
 *
 * //Static querying needs PHP 5.3 or above
 * Food::getById(14);
 * Food::getById_first(14);
 * Food::getById_location_first(14, 'Malaysia');
 * Food::relateFoodType();
 * Food::relateFoodType_first();
 * </code>
 *
 * <p>If you are using static querying methods such as Food::_count(), Food::getById(), you would need to setup the class name in your Model constructor.</p>
 * <code>
 * Doo::loadCore('db/DooModel');
 *
 * class Model extends DooModel{
 *     function __construct(){
 *         parent::$className = __CLASS__;
 *         //OR parent::setupModel(__CLASS__);
 *     }
 * }
 * </code>
 * Please check the database demo MainController's test() method for some example usage.
 *
 * @author Leng Sheng Hong <darkredz@gmail.com>
 * @version $Id: DooModel.php 1000 2009-08-27 10:28:56
 * @package doo.db
 * @since 1.2
 */
class DooModel{
	/**
	 * Determine whether the DB field names should be case sensitive.
	 * @var bool
	 */
	protected static $caseSensitive = false;

	/**
	 * The class name of the Model
	 * @var string
	 */
	protected static $className = __CLASS__;

	/**
	 * Constructor of a Model. Sets the model class properties with a list of keys & values.
	 * @param array $properties Array of data (keys and values) to set the model properties
	 */
	public function __construct($properties=null){
		if($properties!==null){
			foreach($properties as $k=>$v){
				if(in_array($k, $this->_fields))
				$this->{$k} = $v;
			}
		}
	}

	/**
	 * Setup the model. Use if needed with constructor.
	 *
	 * @param string $class Class name of the Model
	 * @param bool $caseSensitive Determine whether the DB field names should be case sensitive.
	 */
	protected function setupModel( $class=__CLASS__, $caseSensitive=false ){
		self::$className = $class;
		self::$caseSensitive = $caseSensitive;
	}

	/**
	 * Validate the Model with the rules defined in getVRules()
	 *
	 * @param string $checkMode Validation mode. all, all_one, skip
	 * @param string $requireMode Require Check Mode. null, nullempty
	 * @return array Return array of errors if exists. Return null if data passes the validation rules.
	 */
	public function validate($checkMode='all', $requireMode='null'){
		//all, all_one, skip
		Doo::loadHelper('DooValidator');
		$v = new DooValidator;
		$v->checkMode = $checkMode;
		$v->requireMode = $requireMode;
		return $v->validate(get_object_vars($this), $this->getVRules());
	}

	/**
	 * Validate the Model with the rules defined in getVRules()
	 *
	 * @param object $model Model object to be validated.
	 * @param string $checkMode Validation mode. all, all_one, skip
	 * @param string $requireMode Require Check Mode. null, nullempty
	 * @return array Return array of errors if exists. Return null if data passes the validation rules.
	 */
	public static function _validate($model, $checkMode='all', $requireMode='null'){
		//all, all_one, skip
		Doo::loadHelper('DooValidator');
		$v = new DooValidator;
		$v->checkMode = $checkMode;
		$v->requiredMode = $requireMode;
		return $v->validate(get_object_vars($model), $model->getVRules());
	}

	//-------------- shorthands --------------------------
	/**
	 * Shorthand for Doo::db()
	 * @return DooSqlMagic
	 */
	public function db(){
		return Doo::db();
	}

	/**
	 * Commits a transaction. Transactions can be nestable.
	 */
	public function commit(){
		Doo::db()->commit();
	}

	/**
	 * Initiates a transaction. Transactions can be nestable.
	 */
	public function beginTransaction(){
		Doo::db()->beginTransaction();
	}

	/**
	 * Rolls back a transaction. Transactions can be nestable.
	 */
	public function rollBack(){
		Doo::db()->rollBack();
	}

	/**
	 * Find a record. (Prepares and execute the SELECT statements)
	 * @param array $opt Associative array of options to generate the SELECT statement. Supported: <i>where, limit, select, param, groupby, asc, desc, custom, asArray</i>
	 * @return mixed A model object or associateve array of the queried result
	 */
	public function find($opt=null){
		return Doo::db()->find($this, $opt);
	}

	/**
	 * Find a record and its associated model. Relational search. (Prepares and execute the SELECT statements)
	 * @param string $rmodel The related model class name.
	 * @param array $opt Associative array of options to generate the SELECT statement. Supported: <i>where, limit, select, param, joinType, groupby, match, asc, desc, custom, asArray, include, includeWhere, includeParam</i>
	 * @return mixed A list of model object(s) or associateve array of the queried result
	 */
	public function relate($rmodel, $opt=null){
		return Doo::db()->relate($this, $rmodel, $opt);
	}

	/**
	 * Combine relational search results (combine multiple relates).
	 *
	 * Example:
	 * <code>
	 * $food = new Food;
	 * $food->relateMany(array('Recipe','Article','FoodType'))
	 * </code>
	 *
	 * @param array $rmodel The related models class names.
	 * @param array $opt Array of options for each related model to generate the SELECT statement. Supported: <i>where, limit, select, param, joinType, groupby, match, asc, desc, custom, asArray, include, includeWhere, includeParam</i>
	 * @return mixed A list of model objects of the queried result
	 */
	public function relateMany($rmodel, $opt=null){
		return Doo::db()->relateMany($this, $rmodel, $opt);
	}

	/**
	 * Expand related models (Tree Relationships).
	 *
	 * Example:
	 * <code>
	 * $recipe = new Recipe;
	 * $recipe->relateExpand(array('Food','Article'))
	 * </code>
	 *
	 * @param array $rmodel The related models class names.
	 * @param array $opt Array of options for each related model to generate the SELECT statement. Supported: <i>where, limit, select, param, joinType, groupby, match, asc, desc, custom, asArray, include, includeWhere, includeParam</i>
	 * @return mixed A list of model objects of the queried result
	 */
	public function relateExpand($rmodel, $opt=null){
		return Doo::db()->relateExpand($this, $rmodel, $opt);
	}

	/**
	 * Adds a new record. (Prepares and execute the INSERT statements)
	 * @return int The inserted record's Id
	 */
	public function insert(){
		return Doo::db()->insert($this);
	}

	/**
	 * Adds a new record with a list of keys & values (assoc array) (Prepares and execute the INSERT statements)
	 * @param array $data Array of data (keys and values) to be insert
	 * @return int The inserted record's Id
	 */
	public function insertAttributes($data){
		return Doo::db()->insertAttributes($this, $data);
	}

	/**
	 * Use insertAttributes() instead
	 * @deprecated deprecated since version 1.3
	 */
	public function insert_attributes($data){
		return $this->insertAttributes($data);
	}

	/**
	 * Adds a new record with its associated models. Relational insert. (Prepares and execute the INSERT statements)
	 * @param array $rmodels A list of associated model objects to be insert along with the main model.
	 * @return int The inserted record's Id
	 */
	public function relatedInsert($rmodels){
		return Doo::db()->relatedInsert($this, $rmodels);
	}

	/**
	 * Update an existing record. (Prepares and execute the UPDATE statements)
	 * @param array $opt Associative array of options to generate the UPDATE statement. Supported: <i>where, limit, field, param</i>
	 * @return int Number of rows affected
	 */
	public function update($opt=NULL){
		return Doo::db()->update($this, $opt);
	}

	/**
	 * Update an existing record with a list of keys & values (assoc array). (Prepares and execute the UPDATE statements)
	 * @param array $opt Associative array of options to generate the UPDATE statement. Supported: <i>where, limit, field, param</i>
	 * @return int Number of rows affected
	 */
	public function update_attributes($data, $opt=NULL){
		return Doo::db()->update_attributes($this, $data, $opt);
	}

	/**
	 * Update an existing record with its associated models. Relational update. (Prepares and execute the UPDATE statements)
	 * @param array $rmodels A list of associated model objects to be updated or insert along with the main model.
	 * @param array $opt Assoc array of options to update the main model. Supported: <i>where, limit, field, param</i>
	 */
	public function relatedUpdate($rmodels, $opt=NULL){
		return Doo::db()->relatedUpdate($this, $rmodels, $opt);
	}

	/**
	 * Returns the last inserted record's id
	 * @return int
	 */
	public function lastInsertId(){
		return Doo::db()->lastInsertId();
	}

	/**
	 * Delete ALL existing records. (Prepares and executes the DELETE statements)
	 */
	public function deleteAll() {
		return Doo::db()->deleteAll($this);
	}

	/**
	 * Delete an existing record. (Prepares and execute the DELETE statements)
	 * @param array $opt Associative array of options to generate the UPDATE statement. Supported: <i>where, limit, param</i>
	 */
	public function delete($opt=NULL){
		return Doo::db()->delete($this, $opt);
	}

	//------- static shorthand methods
	/**
	 * Find a record. (Prepares and execute the SELECT statements)
	 * @param mixed $model The model class name or object to be select
	 * @param array $opt Associative array of options to generate the SELECT statement. Supported: <i>where, limit, select, param, asc, desc, custom, asArray</i>
	 * @return mixed A model object or associateve array of the queried result
	 */
	public static function _find($model, $opt=null){
		return Doo::db()->find($model, $opt);
	}

	/**
	 * Find a record and its associated model. Relational search. (Prepares and execute the SELECT statements)
	 * @param mixed $model The model class name or object to be select.
	 * @param string $rmodel The related model class name.
	 * @param array $opt Associative array of options to generate the SELECT statement. Supported: <i>where, limit, select, param, joinType, match, asc, desc, custom, asArray, include, includeWhere, includeParam</i>
	 * @return mixed A list of model object(s) or associateve array of the queried result
	 */
	public static function _relate($model, $rmodel, $opt=null){
		if($model==null || $model=='')
		$model = self::$className;
		return Doo::db()->relate($model, $rmodel, $opt);
	}

	/**
	 * Adds a new record. (Prepares and execute the INSERT statements)
	 * @param object $model The model object to be insert.
	 * @return int The inserted record's Id
	 */
	public static function _insert($model){
		return Doo::db()->insert($model);
	}

	/**
	 * Adds a new record with a list of keys & values (assoc array) (Prepares and execute the INSERT statements)
	 * @param object $model The model object to be insert.
	 * @param array $data Array of data (keys and values) to be insert
	 * @return int The inserted record's Id
	 */
	public static function _insertAttributes($model, $data){
		return Doo::db()->insert_attributes($model, $data);
	}

	/**
	 * Use DooModel::_insertAttributes() instead.
	 * @deprecated deprecated since version 1.3
	 */
	public static function _insert_attributes($model, $data){
		return Doo::db()->insertAttributes($model, $data);
	}

	/**
	 * Adds a new record with its associated models. Relational insert. (Prepares and execute the INSERT statements)
	 * @param object $model The model object to be insert.
	 * @param array $rmodels A list of associated model objects to be insert along with the main model.
	 * @return int The inserted record's Id
	 */
	public static function _relatedInsert($model, $rmodels){
		return Doo::db()->relatedInsert($model, $rmodels);
	}

	/**
	 * Update an existing record with its associated models. Relational update. (Prepares and execute the UPDATE statements)
	 * @param mixed $model The model object to be updated.
	 * @param array $rmodels A list of associated model objects to be updated or insert along with the main model.
	 * @param array $opt Assoc array of options to update the main model. Supported: <i>where, limit, field, param</i>
	 */
	public static function _relatedUpdate($model, $rmodels, $opt=NULL){
		return Doo::db()->relatedUpdate($model, $rmodels, $opt);
	}

	/**
	 * Update an existing record. (Prepares and execute the UPDATE statements)
	 * @param mixed $model The model object to be updated.
	 * @param array $opt Associative array of options to generate the UPDATE statement. Supported: <i>where, limit, field, param</i>
	 */
	public static function _update($model, $opt=NULL){
		return Doo::db()->update($model, $opt);
	}

	/**
	 * Update an existing record with a list of keys & values (assoc array). (Prepares and execute the UPDATE statements)
	 * @param mixed $model The model object to be updated.
	 * @param array $opt Associative array of options to generate the UPDATE statement. Supported: <i>where, limit, field, param</i>
	 */
	public static function _update_attributes($model, $data, $opt=NULL){
		return Doo::db()->update($model, $data, $opt);
	}

	/**
	 * Returns the last inserted record's id
	 * @return int
	 */
	public static function _lastInsertId(){
		return Doo::db()->lastInsertId();
	}

	/**
	 * Delete ALL existing records. (Prepares and executes the DELETE statement)
	 * @param mixed $model The model object whos records should all be deleted
	 */
	public static function _deleteAll($model) {
		return Doo::db()->deleteAll($model);
	}

	/**
	 * Delete an existing record. (Prepares and execute the DELETE statements)
	 * @param mixed $model The model object to be deleted.
	 * @param array $opt Associative array of options to generate the UPDATE statement. Supported: <i>where, limit, param</i>
	 */
	public static function _delete($model, $opt=NULL){
		return Doo::db()->delete($model, $opt);
	}


	//---------- Useful querying methods such as getOne, count, limit

	/**
	 * Retrieve a list of paginated data. To be used with DooPager
	 *
	 * @param string $limit String for the limit query, eg. '6,10'
	 * @param string $asc Fields to be sorted Ascendingly. Use comma to seperate multiple fields, eg. 'name,timecreated'
	 * @param string $desc Fields to be sorted Descendingly. Use comma to seperate multiple fields, eg. 'name,timecreated'
	 * @param array $options Options for the query. Available options see @see find()
	 * @return mixed A model object or associateve array of the queried result
	 */
	public function limit($limit=1, $asc='', $desc='', $options=null){
		if($asc!='' || $desc!='' || $options!==null){
			$options['limit'] = $limit;
			if($asc!=''){
				$options['asc'] = $asc;
			}
			if($desc!=''){
				$options['desc'] = $desc;
			}
			if($asc!='' && $desc!=''){
				$options['asc'] = $asc;
				$options['custom'] = ','. $desc .' DESC';
			}
			//print_r($options);
			return Doo::db()->find($this, $options);
		}
		return Doo::db()->find($this, array('limit'=>$limit));
	}

	/**
	 * Retrieve model by one record.
	 *
	 * @param array $options Options for the query. Available options see @see find()
	 * @return mixed A model object or associateve array of the queried result
	 */
	public function getOne($options=null){
		if($options!==null){
			$options['limit'] = 1;
			return Doo::db()->find($this, $options);
		}
		return Doo::db()->find($this, array('limit'=>1));
	}

	/**
	 * Retrieve the total records in a table. COUNT()
	 *
	 * @param array $options Options for the query. Available options see @see find() and additional 'distinct' option
	 * @return int total of records
	 */
	public function count($options=null){
		$options['select'] = isset($options['having']) ? $options['select'] . ', ' : '';
		if (isset($options['distinct']) && $options['distinct'] == true) {
			$options['select'] .= 'COUNT(DISTINCT '. $this->_table . '.' . $this->_fields[0] .') as _doototal';
		} else {
			$options['select'] .= 'COUNT('. $this->_table . '.' . $this->_fields[0] .') as _doototal';
		}
		$options['asArray'] = true;
		$options['limit'] = 1;
		$rs = Doo::db()->find($this, $options);
		return $rs['_doototal'];
	}

	//------ static methods for the useful querying method -----

	/**
	 * Retrieve a list of paginated data. To be used with DooPager
	 *
	 * @param object $model The model object to be select.
	 * @param string $limit String for the limit query, eg. '6,10'
	 * @param string $asc Fields to be sorted Ascendingly. Use comma to seperate multiple fields, eg. 'name,timecreated'
	 * @param string $desc Fields to be sorted Descendingly. Use comma to seperate multiple fields, eg. 'name,timecreated'
	 * @param array $options Options for the query. Available options see @see find()
	 * @return mixed A model object or associateve array of the queried result
	 */
	public static function _limit($limit=1, $asc='', $desc='', $options=null){
		if($asc!='' || $desc!='' || $options!==null){
			$options['limit'] = $limit;
			if($asc!=''){
				$options['asc'] = $asc;
			}
			if($desc!=''){
				$options['desc'] = $desc;
			}
			if($asc!='' && $desc!=''){
				$options['asc'] = $asc;
				$options['custom'] = ','. $desc .' DESC';
			}
			return Doo::db()->find(self::$className, $options);
		}
		return Doo::db()->find(self::$className, array('limit'=>$limit));
	}

	/**
	 * Retrieve model by one record.
	 *
	 * @param object $model The model object to be select.
	 * @param array $options Options for the query. Available options see @see find()
	 * @return mixed A model object or associateve array of the queried result
	 */
	public static function _getOne($model=null, $options=null){
		if($model===null)
		$model = new self::$className;

		if($options!==null){
			$options['limit'] = 1;
			return Doo::db()->find($model, $options);
		}
		return Doo::db()->find($model, array('limit'=>1));
	}

	/**
	 * Retrieve the total records in a table. COUNT()
	 *
	 * @param object $model The model object to be select.
	 * @param array $options Options for the query. Available options see @see find() and additional 'distinct' option
	 * @return int total of records
	 */
	public static function _count($model=null, $options=null){
		if($model===null)
		$model = new self::$className;

		$options['select'] = isset($options['having']) ? $options['select'] . ', ' : '';
		if (isset($options['distinct']) && $options['distinct'] == true) {
			$options['select'] .= 'COUNT(DISTINCT '. $model->_table . '.' . $model->_fields[0] .') as _doototal';
		} else {
			$options['select'] .= 'COUNT('. $model->_table . '.' . $model->_fields[0] .') as _doototal';
		}

		$options['asArray'] = true;
		$options['limit'] = 1;
		$rs = Doo::db()->find($model, $options);
		return $rs['_doototal'];
	}


	//--------------- dynamic querying --------------
	public function __call($name, $args){

		// $food->getById( $id );
		// $food->getById(14);
		// $food->getById(14, array('limit'=>1)) ;
		// $food->getById_location(14, 'Malaysia') ;
		// $food->getById_location(14, 'Malaysia', array('limit'=>1)) ;
		if(strpos($name, 'get')===0){
			if(self::$caseSensitive==false){
				$field = strtolower( substr($name,5));
			}else{
				$field = substr($name,5);
			}

			// if end with _first, add 'limit'=>'first' to Option array
			if( substr($name,-6,strlen($field)) == '_first' ){
				$field = str_replace('_first', '', $field);
				$first['limit'] = 1;
			}

			// underscore _ as AND in SQL
			if(strpos($field, '_')!==false){
				$field = explode('_', $field);
			}

			$clsname = get_class($this);
			$obj = new $clsname;

			if(is_string($field)){
				$obj->{$field} = $args[0];

				//if more than the field total, it must be an option array
				if(sizeof($args)>1){
					if(isset($first))
					$args[1]['limit'] = 1;
					return Doo::db()->find($obj, $args[1]);
				}

				if(isset($first)){
					return Doo::db()->find($obj, $first);
				}
				return Doo::db()->find($obj);
			}
			else{
				$i=0;
				foreach($field as $f){
					$obj->{$f} = $args[$i++];
				}

				//if more than the field total, it must be an option array
				if(sizeof($args)>$i){
					if(isset($first))
					$args[$i]['limit'] = 1;
					return Doo::db()->find($obj, $args[$i]);
				}

				if(isset($first)){
					return Doo::db()->find($obj, $first);
				}
				return Doo::db()->find($obj);
			}
		}

		# relateTheRelatedModelClassName
		//$food->relateFoodType();
		//$food->relateFoodType( $optionsOrObject );  if 1 args, it will be option or object
		//$food->relateFoodType( $food, $options );  if more than 1
		# You can get only one by
		//$food->relateFoodType_first();    this adds the 'limit'=>'first' to the Options
		else if(strpos($name, 'relate')===0){
			$relatedClass = substr($name,6);

			// if end with _first, add 'limit'=>'first' to Option array
			if( substr($name,-6,strlen($relatedClass)) == '_first' ){
				$relatedClass = str_replace('_first', '', $relatedClass);
				$first['limit'] = 'first';
				if(sizeof($args)===0){
					$args[0] = $first;
				}
				else{
					if(is_array($args[0])){
						$args[0]['limit'] = 'first';
					}else{
						$args[1]['limit'] = 'first';
					}
				}
			}

			if(sizeof($args)===0){
				return Doo::db()->relate( $this, $relatedClass);
			}
			else if(sizeof($args)===1){
				if(is_array($args[0])){
					return Doo::db()->relate( $this, $relatedClass, $args[0]);
				}else{
					if(isset($first)){
						return Doo::db()->relate( $args[0], $relatedClass, $first);
					}
					return Doo::db()->relate( $args[0], $relatedClass);
				}
			}else{
				return Doo::db()->relate( $args[0], $relatedClass, $args[1]);
			}
		}
	}

	public static function __callStatic($name, $args){

		// Food::getById( $id );
		// Food::getById(14);
		// Food::getById(14, array('limit'=>1)) ;
		// Food::getById_location(14, 'Malaysia') ;
		// Food::getById_location(14, 'Malaysia', array('limit'=>1)) ;
		if(strpos($name, 'get')===0){
			if(self::$caseSensitive==false){
				$field = strtolower( substr($name,5));
			}else{
				$field = substr($name,5);
			}

			// if end with _first, add 'limit'=>'first' to Option array
			if( substr($name,-7,strlen($field)) == '__first' ){
				$field = str_replace('__first', '', $field);
				$first['limit'] = 1;
			}

			// underscore _ as AND in SQL
			if(strpos($field, '__')!==false){
				$field = explode('__', $field);
			}

			$clsname = self::$className;
			$obj = new $clsname;

			if(is_string($field)){
				$obj->{$field} = $args[0];

				//if more than the field total, it must be an option array
				if(sizeof($args)>1){
					if(isset($first))
					$args[1]['limit'] = 1;
					return Doo::db()->find($obj, $args[1]);
				}

				if(isset($first)){
					return Doo::db()->find($obj, $first);
				}
				return Doo::db()->find($obj);
			}
			else{
				$i=0;
				foreach($field as $f){
					$obj->{$f} = $args[$i++];
				}

				//if more than the field total, it must be an option array
				if(sizeof($args)>$i){
					if(isset($first))
					$args[$i]['limit'] = 1;
					return Doo::db()->find($obj, $args[$i]);
				}

				if(isset($first)){
					return Doo::db()->find($obj, $first);
				}
				return Doo::db()->find($obj);
			}
		}

		# relateTheRelatedModelClassName
		// Food::relateFoodType();
		// Food::relateFoodType( $optionsOrObject );  if 1 args, it will be option or object
		// Food::relateFoodType( $food, $options );  if more than 1
		# You can get only one by
		//$food->relateFoodType_first();    this adds the 'limit'=>'first' to the Options
		else if(strpos($name, 'relate')===0){
			$relatedClass = substr($name,6);

			// if end with _first, add 'limit'=>'first' to Option array
			if( substr($name,-7,strlen($relatedClass)) == '__first' ){
				$relatedClass = str_replace('__first', '', $relatedClass);
				$first['limit'] = 'first';
				if(sizeof($args)===0){
					$args[0] = $first;
				}
				else{
					if(is_array($args[0])){
						$args[0]['limit'] = 'first';
					}else{
						$args[1]['limit'] = 'first';
					}
				}
			}

			if(sizeof($args)===0){
				return Doo::db()->relate( self::$className, $relatedClass);
			}
			else if(sizeof($args)===1){
				if(is_array($args[0])){
					return Doo::db()->relate( self::$className, $relatedClass, $args[0]);
				}else{
					if(isset($first)){
						return Doo::db()->relate( $args[0], $relatedClass, $first);
					}
					return Doo::db()->relate( $args[0], $relatedClass);
				}
			}else{
				return Doo::db()->relate( $args[0], $relatedClass, $args[1]);
			}
		}
	}

	public static function __set_state($properties){
		$obj = new self::$className;
		foreach($properties as $k=>$v){
			$obj->{$k} = $v;
		}
		return $obj;
	}

}
